package titt.util.io.compile;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import titt.util.Bytes;
import titt.util.io.loader.BINFileLoader;
import titt.util.io.loader.FileStream;

public class JSCompiler {

	static ScriptableObject compiler;

	private static String getResourceAsString(InputStream is) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(is));
		StringBuffer buffer = new StringBuffer();
		String line;
		while ((line = in.readLine()) != null) {
			buffer.append(line);
			buffer.append("\n");
		}
		return buffer.toString();
	}
	static Pattern PJS_SUBSECTION_PATTERN = Pattern.compile("/\\*<persevere>(.*?)\\*/(.*?)/\\*</persevere>(.*?)\\*/",Pattern.DOTALL);
	static boolean debug = true;

	private static String minify(String source) throws Exception {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		JSMin jsmin = new JSMin(new ByteArrayInputStream(Bytes.asBytes(source)), out);
		jsmin.jsmin();
		String ret = Bytes.asString(out.toByteArray());
		
		while(ret.startsWith("\n"))
			ret = ret.substring(1);
		while(ret.endsWith("\n"))
			ret = ret.substring(0, ret.length()-1);
		
		return ret.replace("</", "<\\/");
	}
	
	private final static BINFileLoader bin = new BINFileLoader();
	
	private static ArrayList<FileStream> sanitize(List<FileStream> files) {
		ArrayList<FileStream> nfiles = new ArrayList<FileStream>();
		for(FileStream fs : files) {
			if(fs.getPath().indexOf("/titt/") != -1 && fs.getPath().indexOf("/titt/server/") == -1 && fs.getName().endsWith(".js") && fs.getName().substring(0,1).equals(fs.getName().substring(0,1).toUpperCase()))
				nfiles.add(fs);
			else 
				try {
					fs.getInputStream().close();
				}
				catch(Exception e) {}
		}
		return nfiles;
	}

	
	public static long TITTlastModifed() {
		
		List<FileStream> files = sanitize(bin.loadDirectoryStream("/titt/"));

		long last = 0;
		for(FileStream fs : files)
			last = Math.max(fs.lastModified(), last);
		return last;
		
	}
	
	public static String compileTITT() throws Exception {		
		List<FileStream> files = sanitize(bin.loadDirectoryStream("/"));
		
		Collections.sort(files, new Comparator<FileStream>() {

			public int compare(FileStream arg0, FileStream arg1) {
				try {
					
					BufferedReader a = new BufferedReader(new InputStreamReader(arg0.getInputStream()));
					BufferedReader b = new BufferedReader(new InputStreamReader(arg1.getInputStream()));
					
					String as = a.readLine();
					String bs = b.readLine();

					a.close();
					b.close();
					
					arg0.close();
					arg1.close();
					
					if(as == null) return  1;
					if(bs == null) return -1;
					
					if(as.startsWith("//dep")) return  1;
					if(bs.startsWith("//dep")) return -1;

					if(as.startsWith("window.")) return  1;
					if(bs.startsWith("window.")) return -1;					

				}
				catch(Exception e) {
					
				}
				return 0;
			}
		});

		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		
		FileStream strands = bin.loadFileStream("/titt/util/io/compile/strands.js");
		
		files.add(0, strands);
		
		for(FileStream f : files) {
			bout.write(f.readFile());
			bout.write('\n');
		}
		
		bout.close();
		
		return minify(Bytes.asString(bout.toByteArray()));

	}
	
	public static String compileStrands() throws Exception {
		FileStream strands = bin.loadFileStream("/titt/util/io/compile/strands.js");
		return minify(Bytes.asString(strands.readFile()));
	}
	
	public static long strandsLastModified() {
		FileStream strands = bin.loadFileStream("/titt/util/io/compile/strands.js");
		return strands.lastModified();
	}
		
	private static String compileString(String source, boolean debug) throws Exception {
		return compileString(source,false,false,false,debug,false,"source");
	}
	public static String compileString(String source) throws Exception {
		return compileString(source,false);
	}

	private static String compileString(String fileContents,boolean getters,boolean setters, boolean expression,boolean debug, boolean js17, String filename) throws Exception {
		synchronized(JSCompiler.class) {
			if (compiler == null) {
				InputStream is = JSCompiler.class.getClassLoader().getResourceAsStream("titt/util/io/compile/compiler.js");
				Context context = Context.enter();
				if (is == null)
					throw new RuntimeException("compiler.js could not be found in the classpath");
				Scriptable global = Context.enter().initStandardObjects();
				String compilerSource = "_frm=false;strands={};\n" + getResourceAsString(is);
				context.evaluateString(global, compilerSource, "compiler", 1, null);
				ScriptableObject strands = (ScriptableObject) global.get("strands", global);
				compiler = (ScriptableObject) strands.get("compiler", strands);
			}
		}
		
		if (fileContents.startsWith("/*persevere-partial")) {
			Matcher m = PJS_SUBSECTION_PATTERN.matcher(fileContents);
			StringBuffer sb = new StringBuffer(); 
			while (m.find())
			{ 
				String openingMassager =m.group(1);
				String njsText = m.group(2);
				String closingMassager= m.group(3); 
				njsText = njsText.replaceAll("//NJS","");
				String targetSource = openingMassager + "\n" +  njsText + "\n" + closingMassager;
				String compiledCode = (String) ScriptableObject.callMethod(compiler, "tryCatchCompile", new Object[]{targetSource,filename,new Boolean(getters),new Boolean(setters),new Boolean(debug),new Boolean(expression),new Boolean(js17)});
				//String compiledCode = (String) Main.shellContextFactory.call(iproxy);
				compiledCode =compiledCode.substring(0,closingMassager.length()==0?compiledCode.length():compiledCode.lastIndexOf(closingMassager));
				compiledCode = compiledCode.replaceAll("\\$","\\\\\\$"); // escape the $ so they don't trigger group capture
				m.appendReplacement(sb,compiledCode);
			}
			m.appendTail(sb);
			return minify(sb.toString());
		}
		else {
			return minify((String) ScriptableObject.callMethod(compiler, "tryCatchCompile", new Object[]{fileContents,filename,new Boolean(getters),new Boolean(setters),new Boolean(debug),new Boolean(expression),new Boolean(js17)}));
		}
	}

	
}
